from OpenGL.GL import *
from OpenGL.GLU import *
from common import *
import numpy
from math import sin, cos, tan, radians

class Camera(object):
	"""
	This class represents the camera system.
	"""
	
	def __init__(self):
		"""
		Constructor.
		""" 
		
		# Camera's absolute position in world coordinates.
		self.position = numpy.array([0.0, 0.0, 10.0, 1])
		
		# Camera's up vector. Should always be unitary.
		self.upVector = numpy.array([0.0, 1.0, 0.0, 0])
		
		# Vector that points to the direction that the camera is looking. Always unitary.
		self.pointer = numpy.array([0.0, 0.0, -1.0, 0])
		
		# Unitary vector perpendicular to the upVecor and the pointer, pointing to the left of the camera. 
		self.leftVector = numpy.array([-1.0, 0.0, 0.0, 0])
		
		# Near/far clipping plane values.
		self.near, self.far = 1, 21
		
		# Width/height aspect of the view.
		self.aspect = 1
		
		# Perspective angle (in degrees).
		self.fovAngle = 45
		
		# Angle used to rotate the camera using the left vector as an axis.
		self.tiltAngle = 15
		
	def setView(self):
		"""
		Sets the camera to the current lookAt position and rotation angle.
		Warning: This method sets the matrix mode to GL_MODELVIEW.
		"""
		
		glMatrixMode(GL_MODELVIEW)
		glLoadIdentity()
		
		glRotate(self.tiltAngle, self.leftVector[X], 
				self.leftVector[Y], self.leftVector[Z])
		
		gluLookAt(self.position[X], self.position[Y], self.position[Z],
				  self.position[X] + self.pointer[X],
				  self.position[Y] + self.pointer[Y],
				  self.position[Z] + self.pointer[Z],
				  self.upVector[X], self.upVector[Y], self.upVector[Z])
		
	def setLens(self, aspect=None, load_identity=True):
		"""
		Sets the lens of the camera.
		"""
		
		if aspect != None:
			self.aspect = aspect
		
		glMatrixMode(GL_PROJECTION)
		if load_identity:
			glLoadIdentity()
		gluPerspective(self.fovAngle, self.aspect, self.near, self.far)
			
	def getScenePosition(self, x, y, depth=None):
		"""
		Gets the coordinates of the mouse in the scene, on a plane
		that is between the far and near planes, according to the depth value.
		"""
		
		if depth is None:
			depth = 10

		return self.position + (depth * self.pointer) 	
		
	def getMapCoordinates(self, worldCoords, size):
		"""
		Gets the nearest map vertex from a given point in world coordinates.
		"""
		
		i = int(worldCoords[X] / size)
		j = int(worldCoords[Y] / size)
		
		return (i,j)
		
	def getViewingConeInterval(self, size):
		"""
		Gets the fraction of the scene that must be rendered.
		"""	
		
		#Z coordinate of the camera.
		height = self.position[Z]
		theta = radians(self.tiltAngle)
		halfFov = radians(self.fovAngle * 0.5)
		
		#Points crossed by the viewing cone.
		y1 = tan(theta - halfFov) * height 
		y2 = tan(theta + halfFov) * height
		
		#Distance from the points on the y-z plane to the viewing cone planes.
		distance1 = self.aspect * ((height / cos(theta)) * sin(halfFov))
		distance2 = (height / cos(theta + halfFov)) * sin(halfFov) * self.aspect
		
		#vertex of the viewing cone on the x-y plane.
		vertex1 = self.position + [distance1, y1, 0, 0]
		vertex2 = self.position + [-distance1, y1, 0, 0]
		vertex3 = self.position + [-distance2, y2, 0, 0]
		vertex4 = self.position + [+distance2, y2, 0, 0]
				
		#Vertex in map coordinates.
		Point0 = self.getMapCoordinates(vertex1, size)
		Point1 = self.getMapCoordinates(vertex2, size)
		Point2 = self.getMapCoordinates(vertex3, size)
		Point3 = self.getMapCoordinates(vertex4, size)
			
		#Area to render.
		iValues = [Point0[X], Point1[X], Point2[X], Point3[X]]
		jValues = [Point0[Y], Point1[Y], Point2[Y], Point3[Y]]
		
		iMin = min(iValues)
		iMax = max(iValues)
		jMin = min(jValues)
		jMax = max(jValues)
		
		return (iMin, iMax, jMin, jMax)
		